package org.tamal.persistence;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import org.hsqldb.server.Server;
import org.tamal.spring.Util;

/**
 * This class does not contain any test cases. It has methods to populate data.
 * @author Tamal Kanti Nath
 */
public final class DatabaseUtil implements Runnable {

    private static boolean executed;
    private static final String SPACE = " ";
    private final List<String> male = new ArrayList<>();
    private final List<String> female = new ArrayList<>();
    private final List<String> surname = new ArrayList<>();
    private final List<String> host = new ArrayList<>();
    private final List<String> street = new ArrayList<>();
    private final Random r = new Random();

    private DatabaseUtil() {
        new Thread(this).start();
    }

    /**
     * Start HSQLDB in server mode and populates dummy data.
     */
    public static synchronized void init() {
        if (!executed) {
            DatabaseUtil populate = new DatabaseUtil();
            int count = Util.getJdbcTemplate()
                .queryForObject("select count(*) from BRANCH", Integer.class);
            if (count == 0) {
                populate.populateData();
            }
            executed = true;
        }
    }

    @Override
    public void run() {
        String args = "--database.0 file:target/hsqldb;user=sa;password=sa"
                + ";hsqldb.sqllog=3;hsqldb.applog=3 --dbname.0 hsqldb";
        Server.main(args.split(SPACE));
    }

    private void populateData() {
        readFiles();
        int customerCount = 50 + r.nextInt(50);
        List<Customer> customers = new ArrayList<>(customerCount);
        for (int i = 1; i <= customerCount; ++i) {
            customers.add(getCustomer());
        }
        Employee ceo = null;
        int branchCount =  5 + r.nextInt(5);
        for (int i = 1; i <= branchCount; ++i) {
            Branch branch = new Branch();
            branch.setName(street.get(r.nextInt(street.size())));
            branch.setAddress(getAddress());
            Set<Account> accounts = new HashSet<>();
            int accountCount = 25 + r.nextInt(25);
            for (int j = 1; j <= accountCount; ++j) {
                accounts.add(getAccount(branch, customers));
            }
            branch.setAccounts(accounts);
            Employee brMgr = null;
            int employeeCount = 5 + r.nextInt(5);
            Set<Employee> employees = new HashSet<>();
            for (int j = 0; j < employeeCount; ++j) {
                Employee employee;
                if (brMgr == null) {
                    employee = getEmployee(branch, ceo);
                    brMgr = employee;
                    if (ceo == null) {
                        ceo = employee;
                    }
                } else {
                    employee = getEmployee(branch, brMgr);
                }
                employees.add(employee);
            }
            branch.setEmployees(employees);
            save(branch);
        }
        /*
         * Either clear the entity manager or refresh the individual persisted
         * object. Otherwise one to many attributes won't be fetched.
         */
        Util.getEntityManager().clear();
    }

    private void readFiles() {
        readFile("Male.txt", male);
        readFile("Female.txt", female);
        readFile("Surname.txt", surname);
        readFile("Host.txt", host);
        readFile("Street.txt", street);
    }

    private static void readFile(String file, List<String> list) {
        String name = "src/test/resources/org/tamal/persistence/" + file;
        try (BufferedReader br = new BufferedReader(new FileReader(name))) {
            while ((name = br.readLine()) != null) {
                list.add(name);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void save(Object object) {
        EntityManager entityManager = Util.getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        entityManager.persist(object);
        transaction.commit();
        // entityManager.refresh(object);
    }

    private Account getAccount(Branch branch, List<Customer> allCustomers) {
        Account account = new Account();
        account.setBranch(branch);
        account.setAmount(new BigDecimal(r.nextInt(1_000_000)));
        Set<Customer> customers = new HashSet<>();
        int customerCount = 1 + r.nextInt(2);
        for (int k = 0; k < customerCount; ++k) {
            customers.add(allCustomers.get(r.nextInt(allCustomers.size())));
        }
        account.setCustomers(customers);
        return account;
    }

    private Customer getCustomer() {
        Customer customer = new Customer();
        boolean sex = r.nextBoolean();
        customer.setName(getName(sex));
        customer.setAddress(getAddress());
        customer.setBirth(getAge());
        customer.setMobile(getMobile());
        customer.setEmail(getEmail(customer.getName()));
        customer.setPan(getPan());
        customer.setSex(sex ? new Character('M') : new Character('F'));
        customer.setUsername(customer.getPan());
        customer.setPassword(getPassword());
        return customer;
    }

    private Employee getEmployee(Branch b, Employee m) {
        Employee employee = new Employee();
        employee.setBranch(b);
        boolean sex = r.nextBoolean();
        employee.setName(getName(sex));
        employee.setAddress(getAddress());
        employee.setBirth(getAge());
        employee.setMobile(getMobile());
        employee.setEmail(getEmail(employee.getName()));
        employee.setSex(sex ? new Character('M') : new Character('F'));
        employee.setManager(m);
        return employee;
    }

    private String getMobile() {
        StringBuffer sb = new StringBuffer("+91 ");
        sb.append(10000 + r.nextInt(90000));
        sb.append(SPACE);
        sb.append(10000 + r.nextInt(90000));
        return sb.toString();
    }

    private String getEmail(String name) {
        String hostname = host.get(r.nextInt(host.size()));
        return name.replace(SPACE, "_") + "@" + hostname;
    }

    private Date getAge() {
        Calendar c = Calendar.getInstance();
        c.roll(Calendar.YEAR, -18 - r.nextInt(80));
        c.set(Calendar.MONTH, 1 + r.nextInt(12));
        c.set(Calendar.DATE, 1 + r.nextInt(28));
        c.set(Calendar.HOUR_OF_DAY, r.nextInt(24));
        c.set(Calendar.MINUTE, r.nextInt(60));
        c.set(Calendar.SECOND, r.nextInt(60));
        c.set(Calendar.MILLISECOND, 0);
        return c.getTime();
    }

    private String getPan() {
        StringBuffer pan = new StringBuffer();
        for (int i = 1; i <= 10; ++i) {
            char ch;
            if (i >= 6 && i <= 9) {
                ch = (char) ('0' + r.nextInt(10));
            } else {
                ch = (char) ('A' + r.nextInt(26));
            }
            pan.append(ch);
        }
        return pan.toString();
    }

    private String getPassword() {
        StringBuffer password = new StringBuffer();
        int passwordLength = 6 + r.nextInt(10);
        for (int i = 1; i <= passwordLength; ++i) {
            password.append((char) (32 + r.nextInt(127 - 32)));
        }
        return password.toString();
    }

    private String getName(boolean sex) {
        String name;
        if (sex) {
            name = male.get(r.nextInt(male.size()));
        } else {
            name = female.get(r.nextInt(female.size()));
        }
        return name + SPACE + surname.get(r.nextInt(surname.size()));
    }

    private String getAddress() {
        String address = r.nextInt(100) + ", "
                + street.get(r.nextInt(street.size()))
                + ", Kolkata 7000" + (10 + r.nextInt(90));
        return address;
    }

}
